﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using System.IO;
using System.Diagnostics;

namespace UnnamedPodcastLibrary
{
    public class PodcastData
    {
        public PodcastData(string fileName)
        {
            this.FileName = fileName;
            this.Lock = new object();
            this._podcasts = new List<Feed>();
        }

        public string FileName { get; private set; }

        public readonly object Lock;
        readonly List<Feed> _podcasts;

        public IEnumerable<Feed> Feeds
        {
            get
            {
                return (from feed in this._podcasts
                        select feed).ToArray();
            }
        }

        public void AddFeed(Uri rssFeed)
        {
            var feed = new Feed(rssFeed) { State = FeedState.UriSaved };

            if (!this._podcasts.Contains(feed))
                this._podcasts.Add(feed);
            
            this.Save();
        }

        public void UpdateFeed(Feed feed)
        {
            if (!this._podcasts.Contains(feed))
                throw new ArgumentOutOfRangeException("feed");

            //update info about feed itself
            var knownFeed = this.FindFeed(feed.DownloadUrl);
            
            knownFeed.Description = feed.Description;
            knownFeed.DownloadUrl = feed.DownloadUrl;
            knownFeed.ImageLocalFileName = feed.ImageLocalFileName;
            knownFeed.ImageUrl = feed.ImageUrl;
            knownFeed.Title = feed.Title;

            //update info about the feed's episodes
            foreach (var episode in feed.Episodes)
            {
                var knownEpisode = FindEpisode(episode.UniqueId);
                if (knownEpisode == null)
                {
                    episode.State = EpisodeState.InfoDownloaded;
                    this.AddEpisode(episode);
                }
                else
                {
                    knownEpisode.DatePublished = episode.DatePublished;
                    knownEpisode.Description = episode.Description;
                    knownEpisode.DownloadUrl = episode.DownloadUrl;
                    knownEpisode.Feed = episode.Feed;
                    knownEpisode.FeedTitle = episode.FeedTitle;
                    knownEpisode.FileSize = episode.FileSize;
                    knownEpisode.FileType = episode.FileType;
                    knownEpisode.Title = episode.Title;
                    knownEpisode.UniqueId = episode.UniqueId;
                }
            }

            this.Save();
        }

        public void RemoveFeed(Uri rssFeed)
        {
            this._podcasts.Remove(new Feed(rssFeed));
            this.Save();
        }

        public void AddEpisode(Episode episode)
        {
            this.FindFeed(episode.Feed).Episodes.Add(episode);
            this.Save();
        }

        internal void RemoveEpisode(Episode episode)
        {
            var feed = this.FindFeed(episode.Feed);
            if (feed != null
                && feed.Episodes.Contains(episode))
            {
                feed.Episodes.Remove(episode);
                this.Save();
            }
        }

        public Feed FindFeed(Uri rssFeed)
        {
            var feedInPodcasts = from feed in this._podcasts
                                 where feed.DownloadUrl.Equals(rssFeed)
                                 select feed;

            if (feedInPodcasts.Count() != 1)
                throw new ArgumentOutOfRangeException("rssFeed");
            return feedInPodcasts.First();
        }

        Episode FindEpisode(string episodeUid)
        {
            var episodes = from feed in this._podcasts
                           from episode in feed.Episodes
                           where episode.UniqueId == episodeUid
                           select episode;

            if (episodes.Count() == 0)
                return null;

            return episodes.First();
        }

        public void SetEpisodeState(string episodeUid, string episodeState)
        {
            var episode = FindEpisode(episodeUid);
            if (episode == null)
                throw new ArgumentOutOfRangeException("episodeUid");
            episode.State = episodeState;
            this.Save();
        }

        public void SetFeedState(Uri rssFeed, string feedState)
        {
            var feed = FindFeed(rssFeed);
            if (feed == null)
                throw new ArgumentOutOfRangeException("rssFeed");
            feed.State = feedState;
            this.Save();
        }

        /// <summary>
        /// Get episodes whose state that they are ready to be
        /// sync'ed (assuming they can be found on disk)
        /// </summary>
        /// <returns></returns>
        public IEnumerable<Episode> GetEpisodesDownloaded()
        {
            return from feed in this._podcasts
                   from episode in feed.Episodes
                   where episode.State == EpisodeState.DownloadCompleted
                   select episode;
        }

        /// <summary>
        /// Get episodes whose state imply that they are ready to be downloaded
        /// </summary>
        /// <returns></returns>
        public IEnumerable<Episode> GetEpisodesToDownload()
        {
            return from feed in this._podcasts
                   from episode in feed.Episodes
                   where episode.State == EpisodeState.DownloadStarted
                   || episode.State == EpisodeState.InfoDownloaded
                       select episode;
        }

        public void Save()
        {
            var xmlFile = new XDocument(
                    new XDeclaration("1.0", "utf-8", "yes"),
                        new XElement("Feeds",
                                from feed in this._podcasts
                                select new XElement("Feed",
                                    new XAttribute("DownloadUrl", feed.DownloadUrl),
                                    new XAttribute("ImageUrl", feed.ImageUrl == null ? string.Empty : feed.ImageUrl.OriginalString),
                                    new XAttribute("Description", feed.Description == null ? string.Empty : feed.Description),
                                    new XElement("Episodes",
                                    from episode in feed.Episodes
                                    select new XElement("Episode",
                                        new XAttribute("DownloadUrl", episode.DownloadUrl.ToString() ?? string.Empty),
                                        new XAttribute("Feed", episode.Feed.ToString() ?? string.Empty),
                                        new XAttribute("Title", episode.Title ?? string.Empty),
                                        new XAttribute("Description", episode.Description ?? string.Empty),
                                        new XAttribute("UniqueId", episode.UniqueId ?? string.Empty),
                                        new XAttribute("FileSize", episode.FileSize)
                                    )
                                    )
                )));
            if (!Directory.Exists(Path.GetDirectoryName(this.FileName)))
                Directory.CreateDirectory(Path.GetDirectoryName(this.FileName));
            xmlFile.Save(this.FileName);
        }

        public void ExportSubscriptions(string opmlPathFileName)
        {
            var xmlFile = new XDocument(
                    new XDeclaration("1.0", "utf-8", "yes"),
                    new XElement("opml",
                        new XAttribute("version", "2.0"),
                        new XElement("head",
                            new XElement("title",
                                new XText("Unnamed Podcast Library subscriptions")),
                                new XElement("dateCreated", 
                                    new XText(DateTime.Now.ToString()))),
                        new XElement("body",
                                from feed in this._podcasts
                                select new XElement("outline",
                                    new XAttribute("text", feed.Title ?? string.Empty),
                                    new XAttribute("title", feed.Title ?? string.Empty),
                                    new XAttribute("type", "rss"),
                                    new XAttribute("xmlUrl", feed.DownloadUrl))
                                    )));

            if (!Directory.Exists(Path.GetDirectoryName(opmlPathFileName)))
                Directory.CreateDirectory(Path.GetDirectoryName(opmlPathFileName));

            xmlFile.Save(opmlPathFileName);
        }

        public void Load()
        {
            this._podcasts.Clear();

            if (!File.Exists(this.FileName))
            {
                this.Save();
                return;
            }
            var document = XDocument.Load(this.FileName);

            this._podcasts.AddRange(
                from feed in document.Descendants("Feed")
                select new Feed(new Uri(feed.Attribute("DownloadUrl").Value)) 
                {
                    Title = feed.Attribute("title") != null 
                    ? feed.Attribute("title").Value ?? string.Empty 
                    : string.Empty,
                    
                    Description = feed.Attribute("description") != null
                    ? feed.Attribute("description").Value ?? string.Empty
                    : string.Empty,

                    ImageUrl = feed.Attribute("ImageUrl") != null && Uri.IsWellFormedUriString(feed.Attribute("ImageUrl").Value as string ?? string.Empty, UriKind.Absolute)
                    ? new Uri((string)feed.Attribute("ImageUrl").Value)
                    : null,
                });
            
            foreach(var feed in this._podcasts)
            {
                feed.Episodes.AddRange(from episode in document.Descendants("Episode")
                                       where episode.Attribute("Feed").Value.Equals(feed.DownloadUrl.OriginalString, StringComparison.OrdinalIgnoreCase)
                                       select new Episode(
                                        episode.Attribute("UniqueId").Value,
                                        new Uri(episode.Attribute("DownloadUrl").Value),
                                        new Uri(episode.Attribute("Feed").Value),
                                        string.IsNullOrEmpty(episode.Attribute("Title").Value) ? "<no title>" : episode.Attribute("Title").Value)
                                        {
                                            Description = episode.Attribute("Description") != null && episode.Attribute("Description").Value as string != null ? (string)episode.Attribute("Description").Value : string.Empty,
                                            FileSize = episode.Attribute("FileSize") != null ? long.Parse(episode.Attribute("FileSize").Value) : Episode.FileSizeUnknown
                                        });
            }
        }

    }

}